
#include "tab.h"

#include <limits>

#include "SkGradientShader.h"

#include "ui_gfx/canvas_skia.h"
#include "ui_gfx/favicon_size.h"
#include "ui_gfx/path.h"
#include "ui_gfx/skbitmap_operations.h"

#include "ui_base/animation/multi_animation.h"
#include "ui_base/animation/throb_animation.h"
#include "ui_base/resource/resource_bundle.h"
#include "ui_base/theme_provider.h"

#include "view/widget/widget.h"
#include "view/widget/tooltip_manager.h"

#include "../wanui_res/resource.h"

#include "defaults.h"

static const int kLeftPadding = 16;
static const int kTopPadding = 6;
static const int kRightPadding = 15;
static const int kBottomPadding = 5;
static const int kDropShadowHeight = 2;
static const int kToolbarOverlap = 1;
static const int kFaviconTitleSpacing = 4;
static const int kTitleCloseButtonSpacing = 3;
static const int kStandardTitleWidth = 175;
static const int kCloseButtonVertFuzz = 0;
static const int kTabIconSize = kFaviconSize;
static const int kCloseButtonHorzFuzz = 5;

// Vertical adjustment to the favicon when the tab has a large icon.
static const int kAppTapFaviconVerticalAdjustment = 2;

// When a non-mini-tab becomes a mini-tab the width of the tab animates. If
// the width of a mini-tab is >= kMiniTabRendererAsNormalTabWidth then the tab
// is rendered as a normal tab. This is done to avoid having the title
// immediately disappear when transitioning a tab from normal to mini-tab.
static const int kMiniTabRendererAsNormalTabWidth =
    browser_defaults::kMiniTabWidth + 30;

// How opaque to make the hover state (out of 1).
static const double kHoverOpacity = 0.33;
static const double kHoverSlideOpacity = 0.5;

// Opacity for non-active selected tabs.
static const double kSelectedTabOpacity = .45;

// Selected (but not active) tabs have their throb value scaled down by this.
static const double kSelectedTabThrobScale = .5;

Tab::TabImage Tab::tab_alpha_ = { 0 };
Tab::TabImage Tab::tab_active_ = { 0 };
Tab::TabImage Tab::tab_inactive_ = { 0 };

// Durations for the various parts of the mini tab title animation.
static const int kMiniTitleChangeAnimationDuration1MS = 1600;
static const int kMiniTitleChangeAnimationStart1MS = 0;
static const int kMiniTitleChangeAnimationEnd1MS = 1900;
static const int kMiniTitleChangeAnimationDuration2MS = 0;
static const int kMiniTitleChangeAnimationDuration3MS = 550;
static const int kMiniTitleChangeAnimationStart3MS = 150;
static const int kMiniTitleChangeAnimationEnd3MS = 800;

// Offset from the right edge for the start of the mini title change animation.
static const int kMiniTitleChangeInitialXOffset = 6;

// Radius of the radial gradient used for mini title change animation.
static const int kMiniTitleChangeGradientRadius = 20;

// Colors of the gradient used during the mini title change animation.
static const SkColor kMiniTitleChangeGradientColor1 = SK_ColorWHITE;
static const SkColor kMiniTitleChangeGradientColor2 =
    SkColorSetARGB(0, 255, 255, 255);

// Hit mask constants.
static const SkScalar kTabCapWidth = 15;
static const SkScalar kTabTopCurveWidth = 4;
static const SkScalar kTabBottomCurveWidth = 3;

// static
const char Tab::kViewClassName[] = "browser/Tab";

////////////////////////////////////////////////////////////////////////////////
// Tab, public:

Tab::Tab(TabController* controller)
: BaseTab(controller),
showing_icon_(false),
showing_close_button_(false),
close_button_color_(0)
{
    InitTabResources();
}

Tab::~Tab() {}

void Tab::StartMiniTabTitleAnimation()
{
    if(!mini_title_animation_.get())
    {
        ui::MultiAnimation::Parts parts;
        parts.push_back(
            ui::MultiAnimation::Part(kMiniTitleChangeAnimationDuration1MS,
            ui::Tween::EASE_OUT));
        parts.push_back(
            ui::MultiAnimation::Part(kMiniTitleChangeAnimationDuration2MS,
            ui::Tween::ZERO));
        parts.push_back(
            ui::MultiAnimation::Part(kMiniTitleChangeAnimationDuration3MS,
            ui::Tween::EASE_IN));
        parts[0].start_time_ms = kMiniTitleChangeAnimationStart1MS;
        parts[0].end_time_ms = kMiniTitleChangeAnimationEnd1MS;
        parts[2].start_time_ms = kMiniTitleChangeAnimationStart3MS;
        parts[2].end_time_ms = kMiniTitleChangeAnimationEnd3MS;
        mini_title_animation_.reset(new ui::MultiAnimation(parts));
        mini_title_animation_->SetContainer(animation_container());
        mini_title_animation_->set_delegate(this);
    }
    mini_title_animation_->Start();
}

void Tab::StopMiniTabTitleAnimation()
{
    if(mini_title_animation_.get())
    {
        mini_title_animation_->Stop();
    }
}

// static
gfx::Size Tab::GetMinimumUnselectedSize()
{
    InitTabResources();

    gfx::Size minimum_size;
    minimum_size.set_width(kLeftPadding + kRightPadding);
    // Since we use bitmap images, the real minimum height of the image is
    // defined most accurately by the height of the end cap images.
    minimum_size.set_height(tab_active_.image_l->height());
    return minimum_size;
}

// static
gfx::Size Tab::GetMinimumSelectedSize()
{
    gfx::Size minimum_size = GetMinimumUnselectedSize();
    minimum_size.set_width(kLeftPadding + kFaviconSize + kRightPadding);
    return minimum_size;
}

// static
gfx::Size Tab::GetStandardSize()
{
    gfx::Size standard_size = GetMinimumUnselectedSize();
    standard_size.set_width(
        standard_size.width() + kFaviconTitleSpacing + kStandardTitleWidth);
    return standard_size;
}

// static
int Tab::GetMiniWidth()
{
    return browser_defaults::kMiniTabWidth;
}

////////////////////////////////////////////////////////////////////////////////
// Tab, protected:

const gfx::Rect& Tab::GetTitleBounds() const
{
    return title_bounds_;
}

const gfx::Rect& Tab::GetIconBounds() const
{
    return favicon_bounds_;
}

void Tab::DataChanged(const TabRendererData& old)
{
    if(data().blocked == old.blocked)
    {
        return;
    }

    if(data().blocked)
    {
        StartPulse();
    }
    else
    {
        StopPulse();
    }
}

////////////////////////////////////////////////////////////////////////////////
// Tab, view::View overrides:

void Tab::OnPaint(gfx::Canvas* canvas)
{
    // Don't paint if we're narrower than we can render correctly. (This should
    // only happen during animations).
    if(width()<GetMinimumUnselectedSize().width() && !data().mini)
    {
        return;
    }

    // See if the model changes whether the icons should be painted.
    const bool show_icon = ShouldShowIcon();
    const bool show_close_button = ShouldShowCloseBox();
    if(show_icon!=showing_icon_ || show_close_button!=showing_close_button_)
    {
        Layout();
    }

    PaintTabBackground(canvas);

    SkColor title_color = SK_ColorBLACK;

    if(!data().mini || width()>kMiniTabRendererAsNormalTabWidth)
    {
        PaintTitle(canvas, title_color);
    }

    if(show_icon)
    {
        PaintIcon(canvas);
    }

    // If the close button color has changed, generate a new one.
    if(!close_button_color_ || title_color != close_button_color_)
    {
        close_button_color_ = title_color;
        ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
        close_button()->SetBackground(close_button_color_,
            rb.GetBitmapNamed(IDR_TAB_CLOSE),
            rb.GetBitmapNamed(IDR_TAB_CLOSE_MASK));
    }
}

void Tab::Layout()
{
    gfx::Rect lb = GetContentsBounds();
    if(lb.IsEmpty())
    {
        return;
    }
    lb.Inset(kLeftPadding, kTopPadding, kRightPadding, kBottomPadding);

    // The height of the content of the Tab is the largest of the favicon,
    // the title text and the close button graphic.
    int content_height = std::max(kFaviconSize, font_height());
    gfx::Size close_button_size(close_button()->GetPreferredSize());
    content_height = std::max(content_height, close_button_size.height());

    // Size the Favicon.
    showing_icon_ = ShouldShowIcon();
    if(showing_icon_)
    {
        // Use the size of the favicon as apps use a bigger favicon size.
        int favicon_top = kTopPadding + content_height / 2 - kFaviconSize / 2;
        int favicon_left = lb.x();
        favicon_bounds_.SetRect(favicon_left, favicon_top,
            kFaviconSize, kFaviconSize);
        if(data().mini && width()<kMiniTabRendererAsNormalTabWidth)
        {
            // Adjust the location of the favicon when transitioning from a normal
            // tab to a mini-tab.
            int mini_delta = kMiniTabRendererAsNormalTabWidth - GetMiniWidth();
            int ideal_delta = width() - GetMiniWidth();
            if(ideal_delta < mini_delta)
            {
                int ideal_x = (GetMiniWidth() - kTabIconSize) / 2;
                int x = favicon_bounds_.x() + static_cast<int>(
                    (1 - static_cast<float>(ideal_delta) /
                    static_cast<float>(mini_delta)) *
                    (ideal_x - favicon_bounds_.x()));
                favicon_bounds_.set_x(x);
            }
        }
    }
    else
    {
        favicon_bounds_.SetRect(lb.x(), lb.y(), 0, 0);
    }

    // Size the Close button.
    showing_close_button_ = ShouldShowCloseBox();
    if(showing_close_button_)
    {
        int close_button_top = kTopPadding + kCloseButtonVertFuzz +
            (content_height - close_button_size.height()) / 2;
        // If the ratio of the close button size to tab width exceeds the maximum.
        close_button()->SetBounds(lb.width() + kCloseButtonHorzFuzz,
            close_button_top, close_button_size.width(),
            close_button_size.height());
        close_button()->SetVisible(true);
    }
    else
    {
        close_button()->SetBounds(0, 0, 0, 0);
        close_button()->SetVisible(false);
    }

    int title_left = favicon_bounds_.right() + kFaviconTitleSpacing;
    int title_top = kTopPadding + (content_height - font_height()) / 2;
    // Size the Title text to fill the remaining space.
    if(!data().mini || width()>=kMiniTabRendererAsNormalTabWidth)
    {
        // If the user has big fonts, the title will appear rendered too far down
        // on the y-axis if we use the regular top padding, so we need to adjust it
        // so that the text appears centered.
        gfx::Size minimum_size = GetMinimumUnselectedSize();
        int text_height = title_top + font_height() + kBottomPadding;
        if(text_height > minimum_size.height())
        {
            title_top -= (text_height - minimum_size.height()) / 2;
        }

        int title_width;
        if(close_button()->IsVisible())
        {
            title_width = std::max(close_button()->x() -
                kTitleCloseButtonSpacing - title_left, 0);
        }
        else
        {
            title_width = std::max(lb.width() - title_left, 0);
        }
        title_bounds_.SetRect(title_left, title_top, title_width, font_height());
    }

    // Certain UI elements within the Tab (the favicon, etc.) are not represented
    // as child Views (which is the preferred method).  Instead, these UI elements
    // are drawn directly on the canvas from within Tab::OnPaint(). The Tab's
    // child Views (for example, the Tab's close button which is a view::Button
    // instance) are automatically mirrored by the mirroring infrastructure in
    // views. The elements Tab draws directly on the canvas need to be manually
    // mirrored if the View's layout is right-to-left.
    title_bounds_.set_x(GetMirroredXForRect(title_bounds_));
}

void Tab::OnThemeChanged()
{
    LoadTabImages();
}

std::string Tab::GetClassName() const
{
    return kViewClassName;
}

bool Tab::HasHitTestMask() const
{
    return true;
}

void Tab::GetHitTestMask(gfx::Path* path) const
{
    DCHECK(path);

    SkScalar h = SkIntToScalar(height());
    SkScalar w = SkIntToScalar(width());

    path->moveTo(0, h);

    // Left end cap.
    path->lineTo(kTabBottomCurveWidth, h - kTabBottomCurveWidth);
    path->lineTo(kTabCapWidth - kTabTopCurveWidth, kTabTopCurveWidth);
    path->lineTo(kTabCapWidth, 0);

    // Connect to the right cap.
    path->lineTo(w - kTabCapWidth, 0);

    // Right end cap.
    path->lineTo(w - kTabCapWidth + kTabTopCurveWidth, kTabTopCurveWidth);
    path->lineTo(w - kTabBottomCurveWidth, h - kTabBottomCurveWidth);
    path->lineTo(w, h);

    // Close out the path.
    path->lineTo(0, h);
    path->close();
}

bool Tab::GetTooltipTextOrigin(const gfx::Point& p, gfx::Point* origin)
{
    origin->set_x(title_bounds_.x() + 10);
    origin->set_y(-view::TooltipManager::GetTooltipHeight() - 4);
    return true;
}

void Tab::OnMouseMoved(const view::MouseEvent& event)
{
    hover_point_ = event.location();
    // We need to redraw here because otherwise the hover glow does not update
    // and follow the new mouse position.
    SchedulePaint();
}

////////////////////////////////////////////////////////////////////////////////
// Tab, private

void Tab::PaintTabBackground(gfx::Canvas* canvas)
{
    if(IsActive())
    {
        PaintActiveTabBackground(canvas);
    }
    else
    {
        if(mini_title_animation_.get() && mini_title_animation_->is_animating())
        {
            PaintInactiveTabBackgroundWithTitleChange(canvas);
        }
        else
        {
            PaintInactiveTabBackground(canvas);
        }

        double throb_value = GetThrobValue();
        if(throb_value > 0)
        {
            canvas->SaveLayerAlpha(static_cast<int>(throb_value * 0xff),
                gfx::Rect(width(), height()));
            canvas->AsCanvasSkia()->drawARGB(0, 255, 255, 255,
                SkXfermode::kClear_Mode);
            PaintActiveTabBackground(canvas);
            canvas->Restore();
        }
    }
}

void Tab::PaintInactiveTabBackgroundWithTitleChange(gfx::Canvas* canvas)
{
    // Render the inactive tab background. We'll use this for clipping.
    gfx::CanvasSkia background_canvas(width(), height(), false);
    PaintInactiveTabBackground(&background_canvas);

    SkBitmap background_image = background_canvas.ExtractBitmap();

    // Draw a radial gradient to hover_canvas.
    gfx::CanvasSkia hover_canvas(width(), height(), false);
    int radius = kMiniTitleChangeGradientRadius;
    int x0 = width() + radius - kMiniTitleChangeInitialXOffset;
    int x1 = radius;
    int x2 = -radius;
    int x;
    if(mini_title_animation_->current_part_index() == 0)
    {
        x = mini_title_animation_->CurrentValueBetween(x0, x1);
    }
    else if(mini_title_animation_->current_part_index() == 1)
    {
        x = x1;
    }
    else
    {
        x = mini_title_animation_->CurrentValueBetween(x1, x2);
    }
    SkPaint paint;
    SkPoint loc = { SkIntToScalar(x), SkIntToScalar(0) };
    SkColor colors[2];
    colors[0] = kMiniTitleChangeGradientColor1;
    colors[1] = kMiniTitleChangeGradientColor2;
    SkShader* shader = SkGradientShader::CreateRadial(
        loc,
        SkIntToScalar(radius),
        colors,
        NULL,
        2,
        SkShader::kClamp_TileMode);
    paint.setShader(shader);
    shader->unref();
    hover_canvas.DrawRectInt(x - radius, -radius, radius * 2, radius * 2, paint);

    // Draw the radial gradient clipped to the background into hover_image.
    SkBitmap hover_image = SkBitmapOperations::CreateMaskedBitmap(
        hover_canvas.ExtractBitmap(), background_image);

    // Draw the tab background to the canvas.
    canvas->DrawBitmapInt(background_image, 0, 0);

    // And then the gradient on top of that.
    if(mini_title_animation_->current_part_index() == 2)
    {
        canvas->SaveLayerAlpha(mini_title_animation_->CurrentValueBetween(255, 0));
        canvas->DrawBitmapInt(hover_image, 0, 0);
        canvas->Restore();
    }
    else
    {
        canvas->DrawBitmapInt(hover_image, 0, 0);
    }
}

void Tab::PaintInactiveTabBackground(gfx::Canvas* canvas)
{
    // The tab image needs to be lined up with the background image
    // so that it feels partially transparent.  These offsets represent the tab
    // position within the frame background image.
    int offset = GetMirroredX() + background_offset_.x();

    int tab_id;
    if(GetWidget() && GetWidget()->GetTopLevelWidget()->ShouldUseNativeFrame())
    {
        tab_id = IDR_THEME_TAB_BACKGROUND_V;
    }
    else
    {
        tab_id = IDR_THEME_TAB_BACKGROUND;
    }

    SkBitmap* tab_bg = GetThemeProvider()->GetBitmapNamed(tab_id);

    TabImage* tab_image = &tab_active_;
    TabImage* tab_inactive_image = &tab_inactive_;
    TabImage* alpha = &tab_alpha_;

    // If the theme is providing a custom background image, then its top edge
    // should be at the top of the tab. Otherwise, we assume that the background
    // image is a composited foreground + frame image.
    int bg_offset_y = /*GetThemeProvider()->HasCustomImage(tab_id) ?
        0 : */background_offset_.y();

    // We need a CanvasSkia object to be able to extract the bitmap from.
    // We draw everything to this canvas and then output it to the canvas
    // parameter in addition to using it to mask the hover glow if needed.
    gfx::CanvasSkia background_canvas(width(), height(), false);

    // Draw left edge.  Don't draw over the toolbar, as we're not the foreground
    // tab.
    SkBitmap tab_l = SkBitmapOperations::CreateTiledBitmap(
        *tab_bg, offset, bg_offset_y, tab_image->l_width, height());
    SkBitmap theme_l =
        SkBitmapOperations::CreateMaskedBitmap(tab_l, *alpha->image_l);
    background_canvas.DrawBitmapInt(theme_l,
        0, 0, theme_l.width(), theme_l.height() - kToolbarOverlap,
        0, 0, theme_l.width(), theme_l.height() - kToolbarOverlap,
        false);

    // Draw right edge.  Again, don't draw over the toolbar.
    SkBitmap tab_r = SkBitmapOperations::CreateTiledBitmap(*tab_bg,
        offset + width() - tab_image->r_width, bg_offset_y,
        tab_image->r_width, height());
    SkBitmap theme_r =
        SkBitmapOperations::CreateMaskedBitmap(tab_r, *alpha->image_r);
    background_canvas.DrawBitmapInt(theme_r,
        0, 0, theme_r.width(), theme_r.height() - kToolbarOverlap,
        width() - theme_r.width(), 0, theme_r.width(),
        theme_r.height() - kToolbarOverlap, false);

    // Draw center.  Instead of masking out the top portion we simply skip over
    // it by incrementing by kDropShadowHeight, since it's a simple rectangle.
    // And again, don't draw over the toolbar.
    background_canvas.TileImageInt(*tab_bg,
        offset + tab_image->l_width,
        bg_offset_y + kDropShadowHeight + tab_image->y_offset,
        tab_image->l_width,
        kDropShadowHeight + tab_image->y_offset,
        width() - tab_image->l_width - tab_image->r_width,
        height() - kDropShadowHeight - kToolbarOverlap - tab_image->y_offset);

    canvas->DrawBitmapInt(background_canvas.ExtractBitmap(), 0, 0);

    if(/*!GetThemeProvider()->HasCustomImage(tab_id) &&*/
        hover_animation() &&
        (hover_animation()->IsShowing() || hover_animation()->is_animating()))
    {
        SkBitmap hover_glow = DrawHoverGlowBitmap(width(), height());
        // Draw the hover glow clipped to the background into hover_image.
        SkBitmap hover_image = SkBitmapOperations::CreateMaskedBitmap(
            hover_glow, background_canvas.ExtractBitmap());
        canvas->DrawBitmapInt(hover_image, 0, 0);
    }

    // Now draw the highlights/shadows around the tab edge.
    canvas->DrawBitmapInt(*tab_inactive_image->image_l, 0, 0);
    canvas->TileImageInt(*tab_inactive_image->image_c,
        tab_inactive_image->l_width, 0,
        width() - tab_inactive_image->l_width -
        tab_inactive_image->r_width,
        height());
    canvas->DrawBitmapInt(*tab_inactive_image->image_r,
        width() - tab_inactive_image->r_width, 0);
}

void Tab::PaintActiveTabBackground(gfx::Canvas* canvas)
{
    int offset = GetMirroredX() + background_offset_.x();
    ui::ThemeProvider* tp = GetThemeProvider();
    DCHECK(tp) << "Unable to get theme provider";

    SkBitmap* tab_bg = GetThemeProvider()->GetBitmapNamed(IDR_THEME_TOOLBAR);

    TabImage* tab_image = &tab_active_;
    TabImage* alpha = &tab_alpha_;

    // Draw left edge.
    SkBitmap tab_l = SkBitmapOperations::CreateTiledBitmap(
        *tab_bg, offset, 0, tab_image->l_width, height());
    SkBitmap theme_l =
        SkBitmapOperations::CreateMaskedBitmap(tab_l, *alpha->image_l);
    canvas->DrawBitmapInt(theme_l, 0, 0);

    // Draw right edge.
    SkBitmap tab_r = SkBitmapOperations::CreateTiledBitmap(*tab_bg,
        offset + width() - tab_image->r_width, 0, tab_image->r_width, height());
    SkBitmap theme_r =
        SkBitmapOperations::CreateMaskedBitmap(tab_r, *alpha->image_r);
    canvas->DrawBitmapInt(theme_r, width() - tab_image->r_width, 0);

    // Draw center.  Instead of masking out the top portion we simply skip over it
    // by incrementing by kDropShadowHeight, since it's a simple rectangle.
    canvas->TileImageInt(*tab_bg,
        offset + tab_image->l_width,
        kDropShadowHeight + tab_image->y_offset,
        tab_image->l_width,
        kDropShadowHeight + tab_image->y_offset,
        width() - tab_image->l_width - tab_image->r_width,
        height() - kDropShadowHeight - tab_image->y_offset);

    // Now draw the highlights/shadows around the tab edge.
    canvas->DrawBitmapInt(*tab_image->image_l, 0, 0);
    canvas->TileImageInt(*tab_image->image_c, tab_image->l_width, 0,
        width() - tab_image->l_width - tab_image->r_width, height());
    canvas->DrawBitmapInt(*tab_image->image_r, width() - tab_image->r_width, 0);
}

SkBitmap Tab::DrawHoverGlowBitmap(int width_input, int height_input)
{
    // Draw a radial gradient to hover_canvas so we can export the bitmap.
    gfx::CanvasSkia hover_canvas(width_input, height_input, false);

    // Draw a radial gradient to hover_canvas.
    int radius = width() / 3;

    SkPaint paint;
    paint.setStyle(SkPaint::kFill_Style);
    paint.setFlags(SkPaint::kAntiAlias_Flag);
    SkPoint loc = { SkIntToScalar(hover_point_.x()),
        SkIntToScalar(hover_point_.y()) };
    SkColor colors[2];
    const ui::SlideAnimation* hover_slide = hover_animation();
    int hover_alpha = 0;
    if(hover_slide)
    {
        hover_alpha = static_cast<int>(255 * kHoverSlideOpacity *
            hover_slide->GetCurrentValue());
    }
    colors[0] = SkColorSetARGB(hover_alpha, 255, 255, 255);
    colors[1] = SkColorSetARGB(0, 255, 255, 255);
    SkShader* shader = SkGradientShader::CreateRadial(
        loc,
        SkIntToScalar(radius),
        colors,
        NULL,
        2,
        SkShader::kClamp_TileMode);
    // Shader can end up null when radius = 0.
    // If so, this results in default full tab glow behavior.
    if(shader)
    {
        paint.setShader(shader);
        shader->unref();
        hover_canvas.DrawRectInt(hover_point_.x() - radius,
            hover_point_.y() - radius,
            radius * 2, radius * 2, paint);
    }
    return hover_canvas.ExtractBitmap();
}

int Tab::IconCapacity() const
{
    if(height() < GetMinimumUnselectedSize().height())
    {
        return 0;
    }
    return (width() - kLeftPadding - kRightPadding) / kFaviconSize;
}

bool Tab::ShouldShowIcon() const
{
    if(data().mini && height()>=GetMinimumUnselectedSize().height())
    {
        return true;
    }
    if(!data().show_icon)
    {
        return false;
    }
    else if(IsActive())
    {
        // The active tab clips favicon before close button.
        return IconCapacity() >= 2;
    }
    // Non-selected tabs clip close button before favicon.
    return IconCapacity() >= 1;
}

bool Tab::ShouldShowCloseBox() const
{
    // The active tab never clips close button.
    return !data().mini && IsCloseable() && (IsActive() || IconCapacity() >= 3);
}

double Tab::GetThrobValue()
{
    bool is_selected = IsSelected();
    double min = is_selected ? kSelectedTabOpacity : 0;
    double scale = is_selected ? kSelectedTabThrobScale : 1;

    if(pulse_animation() && pulse_animation()->is_animating())
    {
        return pulse_animation()->GetCurrentValue() * kHoverOpacity * scale + min;
    }

    if(hover_animation())
    {
        return kHoverOpacity * hover_animation()->GetCurrentValue() * scale + min;
    }

    return is_selected ? kSelectedTabOpacity : 0;
}

////////////////////////////////////////////////////////////////////////////////
// Tab, private:

// static
void Tab::InitTabResources()
{
    static bool initialized = false;
    if(initialized)
    {
        return;
    }

    initialized = true;

    // Load the tab images once now, and maybe again later if the theme changes.
    LoadTabImages();
}

// static
void Tab::LoadTabImages()
{
    // We're not letting people override tab images just yet.
    ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();

    tab_alpha_.image_l = rb.GetBitmapNamed(IDR_TAB_ALPHA_LEFT);
    tab_alpha_.image_r = rb.GetBitmapNamed(IDR_TAB_ALPHA_RIGHT);

    tab_active_.image_l = rb.GetBitmapNamed(IDR_TAB_ACTIVE_LEFT);
    tab_active_.image_c = rb.GetBitmapNamed(IDR_TAB_ACTIVE_CENTER);
    tab_active_.image_r = rb.GetBitmapNamed(IDR_TAB_ACTIVE_RIGHT);
    tab_active_.l_width = tab_active_.image_l->width();
    tab_active_.r_width = tab_active_.image_r->width();

    tab_inactive_.image_l = rb.GetBitmapNamed(IDR_TAB_INACTIVE_LEFT);
    tab_inactive_.image_c = rb.GetBitmapNamed(IDR_TAB_INACTIVE_CENTER);
    tab_inactive_.image_r = rb.GetBitmapNamed(IDR_TAB_INACTIVE_RIGHT);
    tab_inactive_.l_width = tab_inactive_.image_l->width();
    tab_inactive_.r_width = tab_inactive_.image_r->width();
}